/*
 * Copyright (c) 2017. 杭州端点网络科技有限公司.  All rights reserved.
 */

package com.jiantong.web.open.app.user;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.cage.token.RandomTokenGenerator;
import com.google.common.base.*;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.hash.Hashing;
import com.jiantong.common.enums.UserStatus;
import com.jiantong.common.enums.UserType;
import com.jiantong.common.model.BaseLoginUser;
import com.jiantong.common.model.BaseUserProfile;
import com.jiantong.common.util.MD5Util;
import com.jiantong.common.util.UserUtil;
import com.jiantong.user.dto.UserPersonalDataDto;
import com.jiantong.user.enums.*;
import com.jiantong.user.ext.LoginUser;
import com.jiantong.user.model.*;
import com.jiantong.user.model.AppUserRole;
import com.jiantong.user.service.*;
import com.jiantong.web.constants.Sessions;
import com.jiantong.web.constants.SmsCodeType;
import com.jiantong.web.core.component.CaptchaGenerator;
import com.jiantong.web.core.component.user.service.UserDeviceService;
import com.jiantong.web.open.component.MobileChecker;
import io.terminus.boot.rpc.common.annotation.RpcConsumer;
import io.terminus.boot.session.properties.SessionProperties;
import io.terminus.common.model.Paging;
import io.terminus.common.model.Response;
import io.terminus.common.utils.JsonMapper;
import io.terminus.msg.api.app.DeviceType;
import io.terminus.msg.exception.MsgException;
import io.terminus.msg.service.MsgService;
import io.terminus.msg.util.MsgContext;
import io.terminus.pampas.openplatform.annotations.OpenBean;
import io.terminus.pampas.openplatform.annotations.OpenMethod;
import io.terminus.pampas.openplatform.exceptions.OPClientException;
import io.terminus.pampas.openplatform.exceptions.OPServerException;
import io.terminus.session.AFSessionManager;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.NotEmpty;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static io.terminus.common.utils.Arguments.equalWith;
import static io.terminus.common.utils.Arguments.isEmpty;

/**
 * @author : panxin
 */
@Slf4j
@OpenBean
public class UserApis {

    private static final ObjectMapper objectMapper = JsonMapper.nonEmptyMapper().getMapper();

    public static final Character SEP = 'Z';

    private final Splitter AT_SPLITTER = Splitter.on('@').trimResults();

    private static final DateTimeFormatter DTF = DateTimeFormat.forPattern("yyyy-MM-dd");

    private final String hostIpMd5;

    @Autowired(required = false)
    private AFSessionManager sessionManager;
    @Autowired
    private SessionProperties sessionProperties;
    @Autowired
    private CaptchaGenerator captchaGenerator;
    @Autowired(required = false)
    private UserDeviceService userDeviceService;
    @Autowired
    private DepartmentUserReadService departmentUserReadService;
    @Autowired
    private MobileChecker mobileChecker;
    @Autowired
    private MsgService msgService;

    @RpcConsumer
    private UserReadService<User> userReadService;
    @RpcConsumer
    private UserWriteService<User> userWriteService;
    @RpcConsumer
    private UserProfileReadService userProfileReadService;
//    @RpcConsumer
//    private UserProfileWriteService userProfileWriteService;
    @RpcConsumer
    private OrganizationUserReadService organizationUserReadService;
    @RpcConsumer
    private OrganizationUserWriteService organizationUserWriteService;

    @RpcConsumer
    private AppUserRoleReadService appUserRoleReadService;

    public UserApis() {
        String hostIp;
        try {
            hostIp = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            hostIp = UUID.randomUUID().toString();
        }
        hostIpMd5 = Hashing.md5().hashString(hostIp, Charsets.UTF_8).toString().substring(0, 8);
    }

    @OpenMethod(key = "session.id.get", paramNames = {"key"}, httpMethods = RequestMethod.GET)
    public Map<String, String> getSessionId(String key) {
        if (isEmpty(key)) {
            throw new OPClientException("key.miss");
        }
        return ImmutableMap.of("sessionId", generateId(key));
    }




    /**
     * 兼容老版本
     */
    @Deprecated
    @OpenMethod(key = "get.session.id", paramNames = {"key"}, httpMethods = RequestMethod.GET)
    public Map<String, String> getSessionId2(String key) {
        return getSessionId(key);
    }

    /**
     * 登录用户信息
     * @return 用户信息
     */
    @OpenMethod(key = "login.user.info", httpMethods = RequestMethod.GET)
    public LoginUser loginUser() {
        LoginUser loginUser = UserUtil.getCurrentUser();
        log.info("user = {}", loginUser);
        return loginUser;
    }

    @OpenMethod(key = "user.login", paramNames = {"name", "userName", "password", "captcha", "code", "sid", "deviceId"}, httpMethods = RequestMethod.POST)
    public Token login(String name, String userName, String password, String captcha, String code, String sessionId, String deviceId) {
        //name是老版本的用户名
        if (!StringUtils.hasText(userName)) {
            userName = name;
        }

        if (isEmpty(userName)) {
            throw new OPClientException("user.name.miss");
        }
        if (isEmpty(password)) {
            throw new OPClientException("user.password.miss");
        }
        if (isEmpty(sessionId)) {
            throw new OPClientException("session.id.miss");
        }

        //code是老版本的图片验证码
        if (!StringUtils.hasText(captcha)) {
            captcha = code;
        }

        // 当用户次数超过指定次数之后,需要校验图片验证码
        if (arriveErrorLimit(sessionId)) {
            // 判断图片验证码是否匹配
            verifyCaptcha(sessionId, captcha);
        }

        // 用户名密码登录
        User user = doLogin(userName, password, sessionId);

        // 登录成功记录 session
        Map<String, Object> sessionContext = new HashMap<>();
        sessionContext.put(Sessions.USER_ID, user.getId());
        if (StringUtils.hasText(deviceId)) {
            sessionContext.put(Sessions.DEVICE_ID, deviceId);
        }
        sessionManager.save(Sessions.TOKEN_PREFIX, sessionId, sessionContext, Sessions.LONG_INACTIVE_INTERVAL);

        // 清除 limit & code
        sessionManager.deletePhysically(Sessions.LIMIT_PREFIX, sessionId);
        sessionManager.deletePhysically(Sessions.CODE_PREFIX, sessionId);

        // 返回登录的凭证
        Token token = new Token();
        token.setName(userName);
        token.setUserName(userName);
        token.setDomain(sessionProperties.getCookieDomain());
        token.setExpiredAt(DateTime.now().plusSeconds(Sessions.LONG_INACTIVE_INTERVAL)
                .toString(DateTimeFormat.forPattern("yyyyMMddHHmmss")));
        token.setSessionId(sessionId);
        token.setCookieName(sessionProperties.getCookieName());
        token.setDeviceId(deviceId);

        return token;
    }

    /**
     * 用户名密码登录, 不需要手机验证码
     *
     * @param userName 用户名
     * @param name     用户名
     * @param password 密码
     * @param sid      sessionId
     * @param deviceId 设备ID
     * @return 登录信息
     */
    @OpenMethod(
            key = "user.login2",
            paramNames = {"userName", "name", "password", "sid", "deviceId"},
            httpMethods = RequestMethod.POST
    )
    public Token login2(String userName, String name, String password, String sid, String deviceId) {
        //name是老版本的用户名
        if (!StringUtils.hasText(userName)) {
            userName = name;
        }

        if (isEmpty(userName)) {
            throw new OPClientException("user.name.miss");
        }
        if (isEmpty(password)) {
            throw new OPClientException("user.password.miss");
        }
        if (isEmpty(sid)) {
            throw new OPClientException("session.id.miss");
        }

        // 用户名密码登录
        User user = doLogin(userName, password, sid);

        // 登录成功记录 session
        Map<String, Object> sessionContext = new HashMap<>();
        sessionContext.put(Sessions.USER_ID, user.getId());
        if (StringUtils.hasText(deviceId)) {
            sessionContext.put(Sessions.DEVICE_ID, deviceId);
        }
        sessionManager.save(Sessions.TOKEN_PREFIX, sid, sessionContext, Sessions.LONG_INACTIVE_INTERVAL);

        // 清除 limit & code
        sessionManager.deletePhysically(Sessions.LIMIT_PREFIX, sid);
        sessionManager.deletePhysically(Sessions.CODE_PREFIX, sid);

        // 返回登录的凭证
        Token token = new Token();
        token.setName(userName);
        token.setUserName(userName);
        token.setDomain(sessionProperties.getCookieDomain());
        token.setExpiredAt(DateTime.now().plusSeconds(Sessions.LONG_INACTIVE_INTERVAL)
                .toString(DateTimeFormat.forPattern("yyyyMMddHHmmss")));
        token.setSessionId(sid);
        token.setCookieName(sessionProperties.getCookieName());
        token.setDeviceId(deviceId);

        return token;
    }

    @OpenMethod(key = "user.auto.login", paramNames = {"sid", "deviceId"}, httpMethods = RequestMethod.POST)
    public Token autoLogin(String sessionId, String deviceId) {
        if (isEmpty(sessionId)) {
            throw new OPClientException("session.id.miss");
        }
        if (isEmpty(deviceId)) {
            throw new OPClientException("device.id.miss");
        }

        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.TOKEN_PREFIX, sessionId);
        if (snapshot == null || snapshot.size() == 0 || snapshot.get(Sessions.USER_ID) == null) {
            throw new OPClientException(400, "session.id.expired");
        }

        //校验下设备号是否匹配
        checkDeviceId(snapshot, deviceId);

        // refresh
        sessionManager.refreshExpireTime(Sessions.TOKEN_PREFIX, sessionId, Sessions.LONG_INACTIVE_INTERVAL);

        Long uid = Long.parseLong(snapshot.get(Sessions.USER_ID).toString());
        Response<User> res = userReadService.findById(uid);
        if (!res.isSuccess()) {
            log.error("fail to find user by id={},cause:{}",
                    uid, res.getError());
            throw new OPServerException(res.getError());
        }
        User user = res.getResult();

        // 返回登录的凭证
        Token token = new Token();
        token.setName(user.getName());
        token.setDomain(sessionProperties.getCookieDomain());
        token.setExpiredAt(DateTime.now().plusSeconds(Sessions.LONG_INACTIVE_INTERVAL)
                .toString(DateTimeFormat.forPattern("yyyyMMddHHmmss")));
        token.setSessionId(sessionId);
        token.setDeviceId(deviceId);
        token.setCookieName(sessionProperties.getCookieName());

        return token;
    }

    @OpenMethod(key = "user.captcha.get", paramNames = {"sid"}, httpMethods = RequestMethod.GET)
    public Map<String, String> generateCaptcha(String sessionId) {
        if (isEmpty(sessionId)) {
            throw new OPClientException("session.id.miss");
        }
        String code = refreshCaptcha(sessionId);
        byte[] data = captchaGenerator.captcha(code);
        return ImmutableMap.of("captcha", Base64Utils.encodeToString(data));
    }

    /**
     * 兼容老版本
     */
    @Deprecated
    @OpenMethod(key = "get.user.captcher", paramNames = {"sid"})
    public Map<String, String> generateCaptcha2(String sessionId) {
        if (isEmpty(sessionId)) {
            throw new OPClientException("session.id.miss");
        }
        String code = refreshCaptcha(sessionId);
        byte[] data = captchaGenerator.captcha(code);
        return ImmutableMap.of("captcher", Base64Utils.encodeToString(data));
    }

    /**
     * 绑定用户设备号, 因为在登录时可能没法获取到设备号
     * 在user.login和user.auto.login之后, 都要调用这个方法
     *
     * @param sessionId   会话ID
     * @param deviceToken umeng等消息推送关联的设备号
     * @param deviceType  android:1, ios:2
     */
    @OpenMethod(key = "user.device.bind.after.login", paramNames = {"sid", "deviceToken", "deviceType"}, httpMethods = RequestMethod.POST)
    public void bindDevice(String sessionId, String deviceToken, Integer deviceType) {
        if (isEmpty(sessionId)) {
            throw new OPClientException("session.id.miss");
        }
        if (isEmpty(deviceToken)) {
            log.error("bindDevice fail, device={}, cause={}", deviceToken, "device.token.miss");
            throw new OPClientException("device.token.miss");
        }
        if (deviceType == null) {
            log.error("bindDevice fail, device={}, cause={}", deviceToken, "device.type.miss");
            throw new OPClientException("device.type.miss");
        }
        if (UserUtil.getCurrentUser() == null) {
            log.error("bindDevice fail, device={}, cause={}", deviceToken, "user.not.login");
            throw new OPClientException("user.not.login");
        }

        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.TOKEN_PREFIX, sessionId);
        if (snapshot == null || snapshot.size() == 0 || snapshot.get(Sessions.USER_ID) == null) {
            throw new OPClientException(400, "session.id.expired");
        }

        Response<Boolean> resp = userDeviceService.addDevice(UserUtil.getUserId(), getDeviceType(deviceType), deviceToken);
        if (!resp.isSuccess()) {
            log.error("bindDevice fail, device={}, cause={}", deviceToken, resp.getError());
            throw new OPClientException("device.token.bind.fail");
        }

        /*
         * 添加设备号到session中, 在退出时要使用
         */
        snapshot.put(Sessions.DEVICE_TOKEN, deviceToken);
        snapshot.put(Sessions.DEVICE_TYPE, getDeviceType(deviceType));

        sessionManager.save(Sessions.TOKEN_PREFIX, sessionId, snapshot, Sessions.LONG_INACTIVE_INTERVAL);
    }

    @OpenMethod(key = "user.logout", paramNames = {"sid"}, httpMethods = RequestMethod.POST)
    public void logout(String sessionId) {
        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.TOKEN_PREFIX, sessionId);
        String deviceToken = snapshot.get(Sessions.DEVICE_TOKEN) == null ? null : snapshot.get(Sessions.DEVICE_TOKEN).toString();
        String deviceType = snapshot.get(Sessions.DEVICE_TYPE) == null ? null : snapshot.get(Sessions.DEVICE_TYPE).toString();


        sessionManager.deletePhysically(Sessions.TOKEN_PREFIX, sessionId);

        if (deviceToken != null && deviceType != null) {
            //解除设备号
            Response<Boolean> resp = userDeviceService.removeDevice(UserUtil.getUserId(), deviceType, deviceToken);
            if (!resp.isSuccess()) {
                log.error("removeDevice fail when user.logout, device={}, user={}, cause={}",
                        deviceToken, UserUtil.getCurrentUser(), resp.getError());
                throw new OPClientException("device.token.remove.fail");
            }

            //eventBus.post(new AppLogoutEvent(UserUtil.getCurrentUser(), deviceToken));
        }
    }

    @OpenMethod(key = "user.register", paramNames = {"sid", "mobile", "userName", "code", "password"}, httpMethods = RequestMethod.POST)
    public User userSign(String sid, String mobile, String userName, String code, String password) {
        judgeMobile(mobile);

        if (!StringUtils.hasText(code)) {
            throw new OPClientException("user.register.mobile.code.empty");
        }

        if (!StringUtils.hasText(password)) {
            throw new OPClientException("user.register.password.empty");
        }

        checkPasswordFormat(password);

        try {
            validateSmsCode(code, mobile, sid, SmsCodeType.USER_SIGN);
        } catch (IllegalArgumentException e) {
            throw new OPClientException(e.getMessage());
        }

        return registerByMobile(mobile, password, userName);
    }

    @OpenMethod(key = "mobile.code.send", paramNames = {"sid", "mobile", "type", "captcha"}, httpMethods = RequestMethod.POST)
    public void sendMobileCode(String sid, String mobile, Integer type, String captcha) {
        verifyCaptcha(sid, captcha);
        judgeMobile(mobile);

        SmsCodeType smsCodeType = SmsCodeType.from(MoreObjects.firstNonNull(type, 0));
        mobileChecker.checkMobileExistedBySmsCodeType(mobile, smsCodeType);

        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.CODE_PREFIX, sid);
        if (CollectionUtils.isEmpty(snapshot)) {
            snapshot = new HashMap<>();
        }

        // session verify, value = code@time@mobile
        String activateCode = (String) snapshot.get(smsCodeType.getKey());
        if (!Strings.isNullOrEmpty(activateCode)) { //判断是否需要重新发送激活码
            List<String> parts = AT_SPLITTER.splitToList(activateCode);
            long sendTime = Long.parseLong(parts.get(1));
            if (System.currentTimeMillis() - sendTime < TimeUnit.MINUTES.toMillis(1)) {
                log.error("could not send sms, sms only can be sent once in one minute");
                throw new OPClientException("1分钟内只能获取一次验证码");
            }
        }

        String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
        snapshot.put(smsCodeType.getKey(), code + "@" + System.currentTimeMillis() + "@" + mobile);
        // 发送验证码
        doSendSms(mobile, smsCodeType.getTemplate(), code);
        sessionManager.save(Sessions.CODE_PREFIX, sid, snapshot, Sessions.LONG_INACTIVE_INTERVAL);
    }

    /**
     * 兼容老板
     */
    @Deprecated
    @OpenMethod(key = "get.mobile.code", paramNames = {"sid", "mobile", "type", "captcha"})
    public void sendMobielCode2(String sid, String mobile, Integer type, String captcha) {
        sendMobileCode(sid, mobile, type, captcha);
    }

    /**
     * 兼容老版本
     */
    @Deprecated
    @OpenMethod(key = "user.change.password", httpMethods = RequestMethod.POST, paramNames = {"oldPassword", "newPassword", "sid"})
    public Boolean changePassword2(@NotEmpty(message = "oldPassword.not.empty") String oldPassword,
                                   @NotEmpty(message = "newPassword.not.empty") String newPassword) {
        return changePassword(oldPassword, newPassword);
    }

    /**
     * 用户修改密码
     *
     * @param oldPassword 旧密码
     * @param newPassword 新密码
     * @return 是否成功
     */
    @OpenMethod(key = "user.password.change", httpMethods = RequestMethod.POST, paramNames = {"oldPassword", "newPassword"})
    public Boolean changePassword(@NotEmpty(message = "oldPassword.not.empty") String oldPassword,
                                  @NotEmpty(message = "newPassword.not.empty") String newPassword) {

        final Long userId = UserUtil.getUserId();
        if (userId == null) {
            throw new OPClientException("user.not.login");
        }

        verifyPassword(userId, oldPassword);

        User toUpdated = new User();
        toUpdated.setId(userId);
        toUpdated.setPassword(MD5Util.encrypt(newPassword));
        Response<Boolean> updateResp = userWriteService.update(toUpdated);
        if (!updateResp.isSuccess()) {
            log.error("fail to update password for user(id={}),cause:{}",
                    userId, updateResp.getError());
            throw new OPServerException(updateResp.getError());
        }
        return updateResp.getResult();
    }

    /**
     * 忘记密码
     *
     * @param mobile      手机号
     * @param code        验证码
     * @param newPassword 新密码
     * @return 是否成功
     */
    @OpenMethod(key = "user.password.forget", httpMethods = RequestMethod.POST, paramNames = {"mobile", "code", "newPassword", "sid"})
    public Boolean forgetPassword(@NotEmpty(message = "mobile.not.empty") String mobile,
                                  @NotEmpty(message = "code.not.empty") String code,
                                  @NotEmpty(message = "newPassword.not.empty") String newPassword,
                                  @NotEmpty(message = "session.id.miss") String sessionId) {
        //检查密码格式
        checkPasswordFormat(newPassword);

        // 校验手机验证码
        validateSmsCode(code, mobile, sessionId, SmsCodeType.FORGOT_PASSWORD);

        Response<User> findUser = userReadService.findBy(mobile, LoginType.MOBILE);
        if (!findUser.isSuccess()) {
            log.error("fail to find user by mobile={},cause:{}",
                    mobile, findUser.getError());
            throw new OPServerException(findUser.getError());
        }
        User user = findUser.getResult();

        User toUpdated = new User();
        toUpdated.setId(user.getId());
        toUpdated.setPassword(MD5Util.encrypt(newPassword));
        Response<Boolean> updateResp = userWriteService.update(toUpdated);
        if (!updateResp.isSuccess()) {
            log.error("fail to update password for user(id={}),cause:{}",
                    user.getId(), updateResp.getError());
            throw new OPServerException(updateResp.getError());
        }
        return updateResp.getResult();
    }

    /**
     * 兼容老板
     */
    @OpenMethod(key = "user.forget.password", httpMethods = RequestMethod.POST, paramNames = {"mobile", "code", "newPassword", "sid"})
    public Boolean forgetPassword2(@NotEmpty(message = "mobile.not.empty") String mobile,
                                   @NotEmpty(message = "code.not.empty") String code,
                                   @NotEmpty(message = "newPassword.not.empty") String newPassword,
                                   @NotEmpty(message = "session.id.miss") String sessionId) {
        return forgetPassword(mobile, code, newPassword, sessionId);
    }


    @OpenMethod(key = "user.profile.query", httpMethods = RequestMethod.GET)
    public BaseLoginUser findCurrentUserProfile() {
        return UserUtil.getCurrentUser();
    }

    @OpenMethod(key = "user.avatar.update", paramNames = {"avatar"}, httpMethods = RequestMethod.PUT)
    public Boolean updateAvatar(String avatar) {
        final Long userId = UserUtil.getUserId();
        if (userId == null) {
            throw new OPClientException(401, "user.not.login");
        }
        // TODO
        return true;
    }

    @OpenMethod(key = "user.profile.update", paramNames = {"profile"}, httpMethods = RequestMethod.PUT)
    public Boolean updateMyProfile(BaseUserProfile profile) {
        Long userId = UserUtil.getUserId();
        if (userId == null) {
            throw new OPClientException(401, "user.not.login");
        }
        // TODO
        return Boolean.TRUE;
    }

    private void verifyPassword(Long userId, String password) {
        Response<User> findUser = userReadService.findById(userId);
        if (!findUser.isSuccess()) {
            log.error("fail to find user by id={},cause:{}", userId, findUser.getError());
            throw new OPServerException(findUser.getError());
        }
        User user = findUser.getResult();
        if (!MD5Util.match(password, user.getPassword())) {
            throw new OPClientException("user.password.mismatch");
        }
    }

    /**
     * 手机注册
     *
     * @param mobile   手机号
     * @param password 密码
     * @param userName 用户名
     * @return 注册成功之后的用户
     */
    private User registerByMobile(String mobile, String password, String userName) {
        // 检测手机号是否已存在
        Response<Boolean> checkResp = userReadService.checkExist(mobile, LoginType.MOBILE);
        if (!checkResp.isSuccess()) {
            log.error("fail to check mobile({}) if existed,cause:{}",
                    mobile, checkResp.getError());
            throw new OPServerException(checkResp.getError());
        }
        if (checkResp.getResult()) {
            throw new OPClientException("user.mobile.already.exist");
        }
        // 设置用户信息
        User user = new User();
        user.setMobile(mobile);
        user.setPassword(password);
        user.setName(userName);
        user.setStatus(UserStatus.NORMAL.value());
        user.setType(UserType.NORMAL.value());
//        user.setRoles(Collections.singletonList("BUYER"));

        // TODO
        Response<Long> createResp = userWriteService.create(user);

        // prevent password
        user.setPassword(null);
        if (!checkResp.isSuccess()) {
            log.error("fail to create user:{},cause:{}",
                    user, checkResp.getError());
            throw new OPServerException(createResp.getError());
        }
        user.setId(createResp.getResult());
        return user;
    }

    private void doSendSms(String mobile, String template, String code) {
        try {
            msgService.send(mobile, template, MsgContext.of("code", code), null);
        } catch (MsgException e) {
            log.error("sms send failed, mobile={}, cause:{}", mobile, Throwables.getStackTraceAsString(e));
            throw new OPServerException("sms.send.fail");
        }
    }

    private void validateSmsCode(String code, String mobile, String sessionId, SmsCodeType smsCodeType) {
        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.CODE_PREFIX, sessionId);

        // session verify, value = code@time@mobile
        String codeInSession = (String) snapshot.get(smsCodeType.getKey());
        if (!StringUtils.hasText(codeInSession)) {
            throw new OPClientException("sms.code.mismatch");
        }
        String expectedCode = AT_SPLITTER.splitToList(codeInSession).get(0);
        if (!equalWith(code, expectedCode)) {
            throw new OPClientException("sms.code.mismatch");
        }
        String expectedMobile = AT_SPLITTER.splitToList(codeInSession).get(2);
        if (!equalWith(mobile, expectedMobile)) {
            throw new OPClientException("sms.code.mismatch");
        }

        // 如果验证成功则删除之前的code
        sessionManager.deletePhysically(Sessions.CODE_PREFIX, sessionId);
    }

    private void judgeMobile(String mobile) {
        if (!StringUtils.hasText(mobile)) {
            throw new OPClientException("mobile.empty");
        }
        if (!mobile.matches("^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\\d{8}$")) {
            throw new OPClientException("user.mobile.invalid");
        }
    }

    private void checkPasswordFormat(String password) {
        if (!password.matches("[\\s\\S]{6,16}")) {
            throw new OPClientException("user.password.6to16");
        }
    }

    private String getDeviceType(Integer deviceType) {
        String type;
        if (deviceType == 1) {
            type = DeviceType.Android;
        } else if (deviceType == 2) {
            type = DeviceType.Ios;
        } else {
            type = "app";
        }
        return type;
    }

    private void judgeUsername(String username) {
        if (CharMatcher.digit().matchesAllOf(username)
                || CharMatcher.is('@').matchesAnyOf(username)) {
            throw new OPClientException("user.username.invalid");
        }
    }

    //校验设备号是否匹配
    private void checkDeviceId(Map<String, Object> snapshot, String deviceId) {
        if (snapshot.get(Sessions.DEVICE_ID) == null) {
            throw new OPClientException(400, "device.id.expired");
        }

        if (!Objects.equal(deviceId, String.valueOf(snapshot.get(Sessions.DEVICE_ID)))) {
            throw new OPClientException(400, "device.id.not.match");
        }
    }

    private User doLogin(String userName, String password, String sessionId) {
        LoginType loginType;
        if (CharMatcher.DIGIT.matchesAllOf(userName)) {
            loginType = LoginType.MOBILE;
        } else if (CharMatcher.is('@').matchesAnyOf(userName)) {
            loginType = LoginType.EMAIL;
        } else {
            loginType = LoginType.NAME;
        }

        Response<User> result = userReadService.login(userName, password, loginType);
        if (!result.isSuccess()) {
            log.error("fail to login by {},loginType={},cause:{}",
                    loginType, loginType, result.getError());

            plusErrorCount(sessionId);
            refreshCaptcha(sessionId);
            // 登录失败, 记录登录失败次数
            throw new OPClientException(result.getError());
        }
        return result.getResult();
    }

    private String refreshCaptcha(String sessionId) {
        // 将图片验证码存入session
        RandomTokenGenerator tokenGenerator = new RandomTokenGenerator(null, 4, 2);
        String code = tokenGenerator.next();
        Map<String, Object> snapshot = Maps.newHashMap();
        snapshot.put(Sessions.CAPTCHA, code);
        sessionManager.save(Sessions.CODE_PREFIX, sessionId, snapshot, Sessions.SHORT_INACTIVE_INTERVAL);
        return code;
    }

    private void plusErrorCount(String sessionId) {
        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.LIMIT_PREFIX, sessionId);

        if (snapshot.size() == 0) {
            snapshot = Maps.newHashMap();
            snapshot.put("count", 1);
        } else {
            Integer count = (Integer) snapshot.get("count");
            count = count + 1;
            snapshot.put("count", count);
        }
        sessionManager.save(Sessions.LIMIT_PREFIX, sessionId, snapshot, Sessions.MIDDLE_INACTIVE_INTERVAL);
    }

    private void verifyCaptcha(String sessionId, String captcha) {
        if (isEmpty(captcha)) {
            throw new OPClientException("captcha.miss");
        }

        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.CODE_PREFIX, sessionId);
        if (!captcha.equals(snapshot.get(Sessions.CAPTCHA))) {
            throw new OPClientException("captcha.mismatch");
        }
    }

    private boolean arriveErrorLimit(String sessionId) {
        Map<String, Object> snapshot = sessionManager.findSessionById(Sessions.LIMIT_PREFIX, sessionId);
        boolean arriveLimit = false;

        if (snapshot.size() >= 0 && snapshot.get("count") != null) {
            Integer count = (Integer) snapshot.get("count");
            if (count > 2) {
                arriveLimit = true;
            }
        }
        return arriveLimit;
    }

    private String generateId(String key) {
        StringBuilder builder = new StringBuilder(30);
        String clientKey = Hashing.md5().hashString(key, Charsets.UTF_8).toString().substring(0, 8);
        builder.append(clientKey).append(SEP).append(hostIpMd5)
                .append(SEP).append(Long.toHexString(System.currentTimeMillis()))
                .append(SEP).append(UUID.randomUUID().toString().substring(0, 4));
        return builder.toString();
    }

    @Data
    class Token implements Serializable {
        private static final long serialVersionUID = 5130753797196488397L;
        String name;
        String userName;
        String expiredAt;
        String sessionId;
        String cookieName;
        String domain;
        String deviceId;
    }

    /**
     * 更新个人信息 updatePersonalData 养殖端
     * @return 是否成功
     */
    @OpenMethod(
            key = "update.breed.user",
            paramNames = {"userPersonalDataDto"},
            httpMethods = RequestMethod.POST
    )
    public Boolean updateBreedUser(UserPersonalDataDto userPersonalDataDto){
        Long id = UserUtil.getUserId();
        Map<String , Map<String , String>> avatarMap = userPersonalDataDto.getAvatarMap();
        String realName = userPersonalDataDto.getRealName();
        Integer gender = userPersonalDataDto.getGender();
        String identityCardNumber = userPersonalDataDto.getIdentityCardNumber();
        Map<String , Map<String , String>> corporateCharterMap = userPersonalDataDto.getCorporateCharterMap();
        String email = userPersonalDataDto.getEmail();Response<Boolean> response = userWriteService.updatePersonalData(id,avatarMap,realName,gender,identityCardNumber,email,corporateCharterMap);
        if (!response.isSuccess()) {
            log.error("failed to update user",response.getError());
            throw new OPServerException(response.getError());

        }
        return response.getResult();
    }

    /**
     * 更新个人信息 updatePersonalData   政府端
     * @return 是否成功
     */
    @OpenMethod(
            key = "update.governmental.user",
            paramNames = {"userPersonalDataDto"},
            httpMethods = RequestMethod.POST
    )
    public Boolean updateGOVUser(UserPersonalDataDto userPersonalDataDto){
        Long id = UserUtil.getUserId();
        Map<String , Map<String , String>> avatarMap = userPersonalDataDto.getAvatarMap();
        String realName = userPersonalDataDto.getRealName();
        Integer gender = userPersonalDataDto.getGender();
        String identityCardNumber = userPersonalDataDto.getIdentityCardNumber();
        String email = userPersonalDataDto.getEmail();
        Response<Boolean> response = userWriteService.updatePersonalData(id,avatarMap,realName,gender,identityCardNumber,email);
        if (!response.isSuccess()) {
            log.error("failed to update user by id = {}, cause : {}", id, response.getError());
            throw new OPServerException(response.getError());
        }
        return response.getResult();
    }


    /**
     * 查询个人信息
     * @return UserPersonalDataDto
     */
    @OpenMethod(
            key="find.user.personal.data",
            httpMethods = RequestMethod.GET
    )
    public UserPersonalDataDto findUserPersonalData(){
        UserPersonalDataDto userPersonalDataDto = new UserPersonalDataDto();

        LoginUser loginUser = UserUtil.getCurrentUser();    //拿到当前登录人信息
        Long id = loginUser.getId();    //拿到当前登录人的id
        Response<User> userResponse = userReadService.findById(id);
        if(!userResponse.isSuccess()){
            log.error("failed to find user by id = {}, cause : {}", id, userResponse.getError());
            throw new OPServerException(userResponse.getError());
        }
        userPersonalDataDto.setEmail(userResponse.getResult().getEmail());
        userPersonalDataDto.setStatus(userResponse.getResult().getStatus());
        userPersonalDataDto.setMobile(userResponse.getResult().getMobile());
        Response<UserProfile> userProfileResponse = userProfileReadService.findUserProfileById(id); //根据Id对应userprofile中的user_id查找
        if(!userProfileResponse.isSuccess()){
            log.error("failed to find userProfile by id = {}, cause : {}", id, userResponse.getError());
            throw new OPServerException(userResponse.getError());
        }
        userPersonalDataDto.setAvatarMap(userProfileResponse.getResult().getAvatarMap());
        userPersonalDataDto.setGender(userProfileResponse.getResult().getGender());
        userPersonalDataDto.setRealName(userProfileResponse.getResult().getRealName());
        userPersonalDataDto.setIdentityCardNumber(userProfileResponse.getResult().getIdentityCardNumber());
        Response<OrganizationUser> organizationUserResponse = organizationUserReadService.findOrganizationUserByUserId(id); //根据id对应organizationUser中的user_id查找
        if(!organizationUserResponse.isSuccess()){
            log.error("failed to find organizationUser by id = {}, cause : {}", id, userResponse.getError());
            throw new OPServerException(userResponse.getError());
        }
        userPersonalDataDto.setOrganizationUser(organizationUserResponse.getResult());
        //如果是政府端
        if (loginUser.getOrganizationType() == 3){
            //是政府端, 需要给departmentUser 属性
            Response<DepartmentUser> departmentUserResponse = departmentUserReadService.findDepartmentUserByUserId(id);
            if(!departmentUserResponse.isSuccess()){
                log.error("failed to find departmentUser by id = {}, cause : {}", id, userResponse.getError());
                throw new OPServerException(userResponse.getError());
            }
            userPersonalDataDto.setDepartmentUser(departmentUserResponse.getResult());
        }
        return userPersonalDataDto;
    }

    /**
     * 办案人员list来供给前端选择要指派的人.
     */
    @OpenMethod(
            key="find.userList.law",
            paramNames = {"pageNo","pageSize"},
            httpMethods = RequestMethod.GET
    )
    public Paging<User> findUserLaw(Integer pageNo , Integer pageSize){
        //目前拿到所有appUserRoles中 app_role为11的user,因为此角色为案件办理角色
        //查询所有状态为    CASE_TRANSACT(12,"执法办案办案员"), userID

        List<Integer> roleList = new ArrayList<>();//查询哪些角色的执法办案人员
        roleList.add(com.jiantong.user.enums.AppUserRole.CASE_TRANSACT.value());
        Response<Paging<AppUserRole>> response = appUserRoleReadService.pagingLawAppUserRoles(pageNo,pageSize,roleList);
        if (!response.isSuccess()){
            log.error("failed to find AppUserRole by app_role = {}, cause : {}", com.jiantong.user.enums.AppUserRole.CASE_TRANSACT.toString(), response.getError());
            throw new OPServerException(response.getError());
        }
        Paging<AppUserRole> paging = response.getResult();
        List<AppUserRole> list = paging.getData();

        List<User> userList = new ArrayList<>();
        Paging<User> pagingUser= new Paging<>();

        for (AppUserRole appUserRole:list) {
            Response<User> result = userReadService.findById(appUserRole.getUserId());
            if (!result.isSuccess()){
                log.error("failed to find user by id = {}, cause : {}",appUserRole.getId(), response.getError());
                throw new OPServerException(response.getError());
            }
            userList.add(result.getResult());
        }
        pagingUser.setData(userList);
        pagingUser.setTotal(response.getResult().getTotal());
        return pagingUser;
    }
}
